/*    
    OpenMheg - A free MHEG-5 library
    Copyright (C) 2008 Dharmesh Malam

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using System.Text;
using WinForms.Renderer;
using System.Threading;
using System.Windows.Forms;
using System.IO;
using DirectShowLib;
using DCDVBInterop;
using System.Drawing;
using Microsoft.DirectX.Direct3D;
using DirectShowLib.Sample;
using System.Runtime.InteropServices;
using Core.Model.Ingredients.Presentable.Visibles;
using Bitmap=System.Drawing.Bitmap;
using Rectangle=System.Drawing.Rectangle;

namespace WinForms
{
    public sealed class DisplayControl
    {
        private static readonly DisplayControl instance = new DisplayControl();
        private object lockObj = new Object();

        private Engine.MHEGEngine.DisplayRedrawDelegate displayRedrawDelegate;

        public FileInfo DVBFile { get; set; }
        public FileInfo MHPDATAFolder { get; set; }

        //tell c# compiler not to mark type as beforefieldinit
        static DisplayControl () { }
        DisplayControl () { }

        public static DisplayControl Instance
        {
            get
            {
                return instance;
            }
        }

        
        public void Run ()
        {
            Thread.CurrentThread.Name = "GUI";
            displayRedrawDelegate = new Engine.MHEGEngine.DisplayRedrawDelegate( DisplayControl.Instance.Refresh );
            Engine.MHEGEngine.Instance.SetUp( displayRedrawDelegate );
            
            Remote = new Remote(this);
            Application.Run( Remote);
                      
        }

        public void Close()
        {
            Remote.Close();
        }

        private MHEGScreen renderer;

        public MHEGScreen Renderer
        {
            get { return renderer; }
            private set { renderer = value; }
        }

        private Screen screen;

        public Screen Screen
        {
            get { return screen; }
            private set { screen = value; }
        }

        private ScreenJustMHEG screenJustMHEG;

        public ScreenJustMHEG ScreenJustMHEG
        {
            get { return screenJustMHEG; }
            set { screenJustMHEG = value; }
        }

        private Remote remote;

        public Remote Remote
        {
            get { return remote; }
            private set { remote = value; }
        }

        private Thread engineThread;

        public Thread EngineThread
        {
            get { return engineThread; }
            set { engineThread = value; }
        }

        private bool mhegRunning = false;

        public bool MhegRunning
        {
            get { return mhegRunning; }
            set { mhegRunning = value; }
        }


        public void StartJustMHEG (FileInfo mheg)
        {
            Core.Model.Groups.Application app = (Core.Model.Groups.Application)Parser.MHEGParser.ParseMHEG(mheg);

            if ( app != null )
            {

                CloseApp();
                
                Engine.MHEGEngine.Instance.ApplicationRoot = mheg.Directory;
                ThreadStart starter = delegate { Engine.MHEGEngine.Instance.Run( app ); };
                engineThread = new Thread( starter );
                engineThread.Name = "MHEGEngine";
                engineThread.IsBackground = true;

                ScreenJustMHEG = new ScreenJustMHEG();
                ScreenJustMHEG.Show();
                ScreenJustMHEG.Text= Engine.MHEGEngine.Instance.ApplicationRoot.Parent.Name;
                ScreenJustMHEG.Size = new Size( MHEGDisplayConstants.DefaultMHEGPanelSize.Width, MHEGDisplayConstants.DefaultMHEGPanelSize.Height );
                
                engineThread.Start();

                MhegRunning = true;

            }

        }


        public void CloseJustMHEGFromScreen()
        {
            if (mhegRunning)
            {
                if (ScreenJustMHEG != null)
                {
                    ScreenJustMHEG = null;
                }

                Engine.MHEGEngine.Instance.Quit();

                MhegRunning = false;
            }

        }

        public void CloseJustMHEG()
        {
            if (mhegRunning)
            {
                if (ScreenJustMHEG != null)
                {
                    ScreenJustMHEG.Close();
                    ScreenJustMHEG = null;
                }
                
                Engine.MHEGEngine.Instance.Quit();
                
                MhegRunning = false;
            }

        }



        public void StartMHEG()
        {
            if ( !MhegRunning && isTVRunning)
            {
                FileInfo mheg = FindMHEGStart();

                if ( mheg == null )
                    return;

                var app = Parser.MHEGParser.ParseMHEG( mheg ) as Core.Model.Groups.Application;

                if ( app != null )
                {
                    Engine.MHEGEngine.Instance.ApplicationRoot = mheg.Directory;
                    ThreadStart starter = delegate { Engine.MHEGEngine.Instance.Run( app ); };
                    engineThread = new Thread( starter );
                    engineThread.Name = "MHEGEngine";
                    engineThread.IsBackground = true;
                    engineThread.Start();

                    MhegRunning = true;
                }
                                
            }
        }

        internal void CloseMHEG ()
        {
            if ( MhegRunning )
            {
                Engine.MHEGEngine.Instance.Quit();

                if(Screen!=null)
                    Screen.Clear();

                MhegRunning = false;


                videoArea = MHEGDisplayConstants.DefaultMHEGScreenSize;
                MoveVideoWindow( screen.ClientRectangle );

            }

        }
                   

        

        public FileInfo FindMHEGStart ( )
        {

            int index;
            dcdvb.get_ChannelSelected( out index );
            String name;
            dcdvb.get_ChannelInfo( index, out name );

            string path = MHPDATAFolder.FullName +name;

            DirectoryInfo root = new DirectoryInfo( path );
                        
            if ( !root.Exists ) return null;

            foreach ( FileInfo file in root.GetFiles() )
            {
                if ( file.Name == "a" || file.Name == "startup" ) return file;
            }

            return null;
        }

        public void AutoBootMHEG ()
        {
            
        }
        
        public void MHEGUserInput ( int key )
        {
            if(MhegRunning)
                Engine.MHEGEngine.Instance.UserInputEvent( key);

        }
        
        public List<Visible> DisplayStack
        {
          get
            {
                if ( MhegRunning )
                    return Engine.MHEGEngine.Instance.DisplayStack.DrawingStack;
                else
                    return null;
            }

        }

        public void CloseApp ()
        {
            CloseMHEG();
            
            if(Screen != null)
                Screen.Hide();

            CloseClip();
            DestroyScreen();
           

        }


        public void Refresh ( System.Drawing.Rectangle [] rs )
        {
            if ( MhegRunning && isTVRunning)
            {
                Screen.Refresh( rs ,DisplayStack);
               
            }
            else if ( MhegRunning )
            {
                ScreenJustMHEG.Refresh( rs, DisplayStack );

            }

        }
        
        #region Video Stuff


        internal enum PlayState
        {
            Stopped,
            Paused,
            Running,
            Init
        };


        bool isTVRunning = false;
        
        public bool IsTVRunning
        {
            get { return isTVRunning; }
            //set { isTVRunning = value; }
        }

        private const int WMGraphNotify = 0x0400 + 13;
        private const int VolumeFull = 0;
        private const int VolumeSilence = -10000;

        internal IFilterGraph2 graphBuilder = null;
        internal IMediaControl mediaControl = null;
        internal IMediaEventEx mediaEventEx = null;
        internal IVideoWindow videoWindow = null;
        internal IBasicAudio basicAudio = null;
        internal IBasicVideo basicVideo = null;
        internal IMediaSeeking mediaSeeking = null;
        internal IMediaPosition mediaPosition = null;
        internal IVideoFrameStep frameStep = null;

        internal IAMStreamSelect dcdvbstream = null;
        internal IDVBSource dcdvb = null;
        internal IDemuxControl demux = null;

        internal IBaseFilter vmr9 = null;
        internal IVMRMixerBitmap9 mixerBitmap = null;
        internal IVMRWindowlessControl9 windowlessCtrl = null;
        internal IVMRMixerControl9 mixerControl9 = null;

        private bool isFullScreen = false;
        private int currentVolume = VolumeFull;
        private PlayState currentState = PlayState.Stopped;
        
        private IntPtr hDrain = IntPtr.Zero;

        public void StartTV ()
        {
            CreateScreen();
            PlayMovieInWindow( DVBFile.FullName );
            isTVRunning = true;
        }


        public void StopTV ()
        {
            CloseApp();
            isTVRunning = false;

        }


        public void ChannelUp ()
        {
            if ( dcdvb != null )
            {
                CloseMHEG();
                dcdvb.put_NextChannel();
                UpdateScreenTitle();
            }

        }

        public void ChannelDown ()
        {
            if ( dcdvb != null )
            {
                CloseMHEG();
                dcdvb.put_PreviousChannel();
                UpdateScreenTitle();
            }
        }

        public void ShowOSD ()
        {
            if(dcdvb != null)
                dcdvb.ShowOSD();
        }



        private void CreateScreen ()
        {
            if ( screen == null )
            {
                screen = new Screen();
                screen.Show();
                Screen.Size = new Size( MHEGDisplayConstants.DefaultMHEGPanelSize.Width, MHEGDisplayConstants.DefaultMHEGPanelSize.Height );        
            }
        }



        private void DestroyScreen ()
        {
            if ( screen != null )
            {
                screen.CleanUp();
                screen.Close();
                screen = null;
            }
        }



        private void PlayMovieInWindow ( string filename )
        {
            int hr = 0;

            if ( filename == string.Empty )
                return;
                        
            this.graphBuilder = ( IFilterGraph2 ) new FilterGraph();

            vmr9 = ( IBaseFilter ) new VideoMixingRenderer9();

            ConfigureVMR9InWindowlessMode();
            InitializeDirect3D();
            
            hr = graphBuilder.AddFilter( vmr9, "Video Mixing Renderer 9" );
            DsError.ThrowExceptionForHR( hr );
            
            mixerBitmap = ( IVMRMixerBitmap9 ) vmr9;

            // Have the graph builder construct its the appropriate graph automatically

            hr = this.graphBuilder.RenderFile( filename, null );
            DsError.ThrowExceptionForHR( hr );

            // QueryInterface for DirectShow interfaces
            this.mediaControl = ( IMediaControl ) this.graphBuilder;
            this.mediaEventEx = ( IMediaEventEx ) this.graphBuilder;
            this.mediaSeeking = ( IMediaSeeking ) this.graphBuilder;
            this.mediaPosition = ( IMediaPosition ) this.graphBuilder;


            IEnumFilters filters;
            this.graphBuilder.EnumFilters( out filters );
            IBaseFilter [] ppFilter = new IBaseFilter [1];
            IntPtr pcFectched = IntPtr.Zero; //FIXME
            while ( filters.Next( 1, ppFilter, pcFectched ) != 1 && dcdvb == null && dcdvbstream == null && demux == null )
            {
                dcdvbstream = ppFilter [0] as IAMStreamSelect;
                dcdvb = ppFilter [0] as IDVBSource;
                demux = ppFilter [0] as IDemuxControl;
            }
            
            // Query for video interfaces, which may not be relevant for audio files
            this.videoWindow = this.graphBuilder as IVideoWindow;
            this.basicVideo = this.graphBuilder as IBasicVideo;
            mixerControl9 = ( IVMRMixerControl9 ) vmr9;

            // Query for audio interfaces, which may not be relevant for video-only files
            this.basicAudio = this.graphBuilder as IBasicAudio;
            
            // Have the graph signal event via window callbacks for performance
            hr = this.mediaEventEx.SetNotifyWindow( screen.Handle, WMGraphNotify, IntPtr.Zero );
            //DsError.ThrowExceptionForHR( hr );


            // Call the resize handler to configure the output size
            MoveVideoWindow( screen.ClientRectangle );
            
            this.isFullScreen = false;

            UpdateScreenTitle();

            UnZoom();
            //SetMixerSettings(screen.bitmap);
            

            // Run the graph to play the media file
            hr = this.mediaControl.Run();
            DsError.ThrowExceptionForHR( hr );

            this.currentState=PlayState.Running;
        }

        private void UpdateScreenTitle ()
        {
            int index;
            dcdvb.get_ChannelSelected( out index );
            String name;
            dcdvb.get_ChannelInfo( index, out name );

            Screen.Text = name;
        }

        private void ConfigureVMR9InWindowlessMode ()
        {
            int hr = 0;

            IVMRFilterConfig9 filterConfig = ( IVMRFilterConfig9 ) vmr9;

            // Not really needed for VMR9 but don't forget calling it with VMR7
            //hr = filterConfig.SetNumberOfStreams( 1 );
            //DsError.ThrowExceptionForHR( hr );

            // Change VMR9 mode to Windowless
            hr = filterConfig.SetRenderingMode( VMR9Mode.Windowless );
            DsError.ThrowExceptionForHR( hr );

            windowlessCtrl = ( IVMRWindowlessControl9 ) vmr9;
            

            
            // Set "Parent" window
            hr = windowlessCtrl.SetVideoClippingWindow( screen.Handle );
            DsError.ThrowExceptionForHR( hr );

            // Set Aspect-Ratio
            hr = windowlessCtrl.SetAspectRatioMode( VMR9AspectRatioMode.LetterBox );
            DsError.ThrowExceptionForHR( hr );
                         

        }

        internal void MoveVideoWindow ( System.Drawing.Rectangle client )
        {
            int hr = 0;

            if ( windowlessCtrl != null )
            {
                hr = windowlessCtrl.SetVideoPosition( null, DsRect.FromRectangle( client ) );
                DsError.ThrowExceptionForHR( hr );
            }


            if ( mixerControl9 != null )
            {
                NormalizedRect r = new NormalizedRect();
                r.top =  ( float ) VideoArea.Y /( float ) MHEGDisplayConstants.DefaultMHEGScreenSize.Height;
                r.left =   ( float ) VideoArea.X /( float ) MHEGDisplayConstants.DefaultMHEGScreenSize.Width;
                r.right =  (float)(VideoArea.X + VideoArea.Width )/ (float) MHEGDisplayConstants.DefaultMHEGScreenSize.Width;
                r.bottom= ( float ) ( VideoArea.Y + VideoArea.Height )/( float ) MHEGDisplayConstants.DefaultMHEGScreenSize.Height;
                
              
               // hr = basicVideo.SetDestinationPosition( VideoArea.X, VideoArea.Y, VideoArea.Width, VideoArea.Height );
                

                //Rectangle r = new Rectangle( 0,0,360 ,288 );
                hr = mixerControl9.SetOutputRect(0,ref r);
                DsError.ThrowExceptionForHR( hr );
            }

        }
        
        public void CloseClip ()
        {
            int hr = 0;

            // Stop media playback
            if ( this.mediaControl != null )
                hr = this.mediaControl.Stop();

            // Clear global flags
            this.currentState = PlayState.Stopped;
            this.isFullScreen = false;

            // Free DirectShow interfaces
            CloseInterfaces();

            // No current media state
            this.currentState = PlayState.Init;

        }


        private void CloseInterfaces ()
        {
            int hr = 0;

            try
            {
                lock ( this )
                {
                    // Relinquish ownership (IMPORTANT!) after hiding video window

                    //hr = this.videoWindow.put_Visible( OABool.False );
                    //DsError.ThrowExceptionForHR( hr );
                    //hr = this.videoWindow.put_Owner( IntPtr.Zero );
                    //DsError.ThrowExceptionForHR( hr );


                    if ( this.mediaEventEx != null )
                    {
                        hr = this.mediaEventEx.SetNotifyWindow( IntPtr.Zero, 0, IntPtr.Zero );
                        DsError.ThrowExceptionForHR( hr );
                    }

                    // Release and zero DirectShow interfaces
                    if ( this.mediaEventEx != null )
                        this.mediaEventEx = null;
                    if ( this.mediaSeeking != null )
                        this.mediaSeeking = null;
                    if ( this.mediaPosition != null )
                        this.mediaPosition = null;
                    if ( this.mediaControl != null )
                        this.mediaControl = null;
                    if ( this.basicAudio != null )
                        this.basicAudio = null;
                    if ( this.basicVideo != null )
                        this.basicVideo = null;
                    if ( this.videoWindow != null )
                        this.videoWindow = null;
                    if ( this.frameStep != null )
                        this.frameStep = null;
                    if ( this.graphBuilder != null )
                    { Marshal.ReleaseComObject( this.graphBuilder ); this.graphBuilder = null; }

                    if ( vmr9 != null )
                    {
                        Marshal.ReleaseComObject( vmr9 );
                        vmr9 = null;
                        windowlessCtrl = null;
                        mixerBitmap = null;
                    }

                    if ( presentParams!= null )
                        presentParams = null;

                    if ( surface!= null ) { surface.Dispose(); surface = null; }

                    if ( device != null ) { device.Dispose(); device = null; }
                    
                    if ( dcdvb != null )
                        dcdvb = null;
                    if ( dcdvbstream != null )
                        dcdvbstream = null;
                    if ( demux != null )
                        demux = null;

                    if(mixerControl9 != null)
                        mixerControl9 = null;


                    GC.Collect();
                }
            }
            catch
            {
            }
        }

        
        #endregion
        
        #region Mixer

        // Managed Direct3D magic number to retrieve unmanaged Direct3D interfaces
        private const int DxMagicNumber = -759872593;
        private Device device = null; // A Managed Direct3D device
        private PresentParameters presentParams;
        private Surface surface = null; // A Direct3D suface filled with alphaBitmap
        private IntPtr unmanagedSurface; // A pointer on the unmanaged surface
        //private Bitmap bitmap;
        
        VMR9AlphaBitmap alphaBmp;

        public void SetMixerSettings (Bitmap bitmap)
        {
            int hr = 0;
            
            //if ( !mixerEnabled ) // Did the user disable the bitmap ?
            //{
            //    // Get current Alpha Bitmap Parameters
            //  hr = mixerBitmap.GetAlphaBitmapParameters( out alphaBmp );
            //  DsError.ThrowExceptionForHR( hr );

            //    // Disable them
            //    alphaBmp.dwFlags = VMR9AlphaBitmapFlags.Disable;

            //    // Update the Alpha Bitmap Parameters
            //    hr = mixerBitmap.UpdateAlphaBitmapParameters( ref alphaBmp );
            //    DsError.ThrowExceptionForHR( hr );

            //    return;
            //}

            //surface = new Surface( device, bitmap, Pool.SystemMemory );
            //unmanagedSurface = surface.GetObjectByValue( DxMagicNumber );


            //// Set Alpha Bitmap Parameters for using a Direct3D surface
            //alphaBmp = new VMR9AlphaBitmap();
            
            //alphaBmp.dwFlags = VMR9AlphaBitmapFlags.EntireDDS;
            //alphaBmp.pDDS = unmanagedSurface;
            //alphaBmp.rDest = GetDestRectangle();
            //alphaBmp.fAlpha = 1.0f;
            //// Note : Alpha values from the bitmap are cumulative with the fAlpha parameter.
            //// Example : texel alpha = 128 (50%) & fAlpha = 0.5f (50%) = effective alpha : 64 (25%)

            //// Set Alpha Bitmap Parameters
            //hr = mixerBitmap.SetAlphaBitmap( ref alphaBmp );
            //DsError.ThrowExceptionForHR( hr );
            ////}


            Graphics g = Graphics.FromImage( bitmap );
            IntPtr hdc = g.GetHdc();
            IntPtr memDC = NativeMethodes.CreateCompatibleDC( hdc );
            IntPtr hBitmap = bitmap.GetHbitmap();
            NativeMethodes.SelectObject( memDC, hBitmap );

            // Set Alpha Bitmap Parameters for using a GDI DC
            alphaBmp = new VMR9AlphaBitmap();
            alphaBmp.dwFlags = VMR9AlphaBitmapFlags.hDC | VMR9AlphaBitmapFlags.SrcColorKey | VMR9AlphaBitmapFlags.FilterMode;
            alphaBmp.hdc = memDC;
            alphaBmp.rSrc = recSrc;
            alphaBmp.rDest = GetDestRectangle();
            alphaBmp.clrSrcKey = ColorTranslator.ToWin32( Color.Green );
            alphaBmp.dwFilterMode = VMRMixerPrefs.PointFiltering;
            alphaBmp.fAlpha = 1f;

            // Set Alpha Bitmap Parameters
            hr = mixerBitmap.SetAlphaBitmap( ref alphaBmp );
            DsError.ThrowExceptionForHR( hr );

            // Release GDI handles
            NativeMethodes.DeleteObject( hBitmap );
            NativeMethodes.DeleteDC( memDC );
            g.ReleaseHdc( hdc );
            g.Dispose();


        }

        private Rectangle videoArea = MHEGDisplayConstants.DefaultMHEGScreenSize;

        public Rectangle VideoArea
        {
            get { return videoArea; }
            set { if ( screen != null ) { videoArea = value; MoveVideoWindow( screen.ClientRectangle ); } }
        }
        
        public void ZoomTop ()
        {
            if ( screen !=null )
            {
                recSrc.top = 0;
                recSrc.left = 0;
                recSrc.right = screen.bitmap.Size.Width;
                recSrc.bottom = 288;//288;//( int ) Math.Round( ( double ) screen.bitmap.Size.Height / 2 );

                SetMixerSettings( screen.bitmap );
            }
        }

        public void ZoomBottom ()
        {
            if ( screen !=null )
            {
                
                //recSrc.top = 100;//( int ) Math.Round( ( double ) screen.bitmap.Size.Height / 2 );
                //recSrc.left = 0;
                //recSrc.right = 300;//screen.bitmap.Size.Width;
                //recSrc.bottom = 300;// screen.bitmap.Size.Height;

                //SetMixerSettings( screen.bitmap );
            }
        }

        public void UnZoom ()
        {
            if ( screen !=null )
            {
                recSrc.top = 0;
                recSrc.left = 0;
                recSrc.right = screen.bitmap.Size.Width;
                recSrc.bottom = screen.bitmap.Size.Height;

                SetMixerSettings( screen.bitmap );
            }
        }

        DsRect recSrc = new DsRect();

        private NormalizedRect GetDestRectangle ()
        {
           // int hr = 0;
           // int width, height, arW, arH;
            NormalizedRect rect = new NormalizedRect();

            //  hr = Display.Instance.Remote.windowlessCtrl.GetNativeVideoSize( out width, out height, out arW, out arH );
            //   DsError.ThrowExceptionForHR( hr );

            rect.top = 0.0f;
            rect.left = 0.0f;
            rect.bottom = 1f;
            rect.right = 1f;

            return rect;
        }
        
        public void InitializeDirect3D ()
        {
            // Basic Presentation Parameters...
            presentParams = new PresentParameters();
            presentParams.Windowed = true;
            presentParams.SwapEffect = SwapEffect.Discard;

            // Assume a hardware Direct3D device is available
            // Add MultiThreaded to be safe. Each DirectShow filter runs in a separate thread...

            device = new Device(
                0,
                DeviceType.Hardware,
                Screen.Handle,
                CreateFlags.HardwareVertexProcessing,
                presentParams
                );

            // Create a surface from our alpha bitmap
            //surface = new Surface( device, bitmap, Pool.Default );
            // Get the unmanaged pointer

            //unmanagedSurface = surface.GetObjectByValue( DxMagicNumber );
        }

        public void DoSurface ()
        {
            //surface = new Surface( device, bitmap, Pool.SystemMemory );
            //surface.
            //unmanagedSurface = surface.GetObjectByValue( DxMagicNumber );

            //SetMixerSettings();

            // int hr = Display.Instance.Remote.mixerBitmap.UpdateAlphaBitmapParameters( ref alphaBmp );
            //   DsError.ThrowExceptionForHR( hr );
        }

        internal void Mix ( Bitmap image )
        {
            //bitmap = image;
            SetMixerSettings(image);
        }
        
        #endregion
        
    }
}
